{% extends "console/layout.html" %}
{% block title %}Testing · {{site_name|default('RustPBX')}}{% endblock %}
{% block content %}
<div class="p-6">
    <div class="mx-auto max-w-7xl space-y-6" x-data='testingConsole({{ test_data | tojson }})'>
        <header class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
            <div class="space-y-1">
                <p class="text-xs font-semibold uppercase tracking-wide text-sky-600">Operational validation</p>
                <h1 class="text-2xl font-semibold text-slate-900">Testing & diagnostics</h1>
                <p class="text-sm text-slate-500">Verify trunk reachability, routing decisions, and place synthetic
                    calls without leaving the console.</p>
            </div>
            <div class="text-xs text-slate-500">
                Last audit:
                <span class="font-semibold text-slate-700" x-text="formatDateTime(lastAudit)"></span>
            </div>
        </header>

        <nav
            class="inline-flex rounded-lg border border-slate-200 bg-slate-50 p-1 text-xs font-semibold text-slate-600">
            <template x-for="tab in tabs" :key="tab.id">
                <button type="button" @click="selectTab(tab.id)" class="rounded-md px-3 py-1.5 transition"
                    :class="activeTab === tab.id ? 'bg-white text-sky-700 shadow-sm' : 'text-slate-500 hover:text-slate-700'">
                    <span x-text="tab.label"></span>
                </button>
            </template>
        </nav>

        <section x-show="activeTab === 'trunks'" x-cloak x-transition.opacity class="space-y-6">
            <div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
                <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
                    <div>
                        <h2 class="text-base font-semibold text-slate-900">Trunk ingress validation</h2>
                        <p class="text-xs text-slate-500">Confirm whether hostnames or IPs resolve into configured
                            inbound definitions and permitted directions.</p>
                    </div>
                    <div
                        class="inline-flex items-center gap-2 rounded-full bg-emerald-50 px-3 py-1 text-[11px] font-semibold text-emerald-600 ring-1 ring-emerald-200">
                        <span class="h-1.5 w-1.5 rounded-full bg-emerald-500"></span>
                        Live evaluation
                    </div>
                </div>

                <div class="mt-5 divide-y divide-slate-100">
                    <template x-for="trunk in trunks" :key="trunk.id">
                        <article class="py-4"
                            :class="trunkTests[trunk.id]?.loading ? 'md:pl-3 md:border-l-2 md:border-sky-200 md:bg-sky-50/40 rounded-lg' : ''">
                            <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
                                <div class="flex flex-col gap-2">
                                    <div class="flex flex-wrap items-center gap-2">
                                        <h3 class="text-sm font-semibold text-slate-900" x-text="trunk.label"></h3>
                                        <span
                                            class="rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600"
                                            x-text="trunk.id"></span>
                                        <span
                                            class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                            :class="statusPill(trunk.status)">
                                            <span class="h-1.5 w-1.5 rounded-full"
                                                :class="statusDot(trunk.status)"></span>
                                            <span x-text="statusLabel(trunk.status)"></span>
                                        </span>
                                    </div>
                                    <div class="flex flex-wrap gap-4 text-xs text-slate-500">
                                        <div>
                                            <span class="font-semibold text-slate-700">Latency:</span>
                                            <span x-text="trunk.latency_ms + ' ms'"></span>
                                        </div>
                                        <div>
                                            <span class="font-semibold text-slate-700">Loss:</span>
                                            <span x-text="trunk.packet_loss_percent + '%' "></span>
                                        </div>
                                        <div>
                                            <span class="font-semibold text-slate-700">Concurrency:</span>
                                            <span
                                                x-text="trunk.concurrency.current + ' / ' + trunk.concurrency.limit"></span>
                                        </div>
                                        <div>
                                            <span class="font-semibold text-slate-700">Last probe:</span>
                                            <span x-text="formatDateTime(trunk.last_test_at)"></span>
                                        </div>
                                    </div>
                                    <div class="flex flex-wrap items-center gap-2 text-[11px] text-slate-500">
                                        <span class="rounded-full bg-slate-100 px-2 py-0.5 font-semibold text-slate-600"
                                            x-text="trunk.direction.join(' / ') || 'Unknown'"></span>
                                        <span x-text="trunk.egress"></span>
                                    </div>
                                    <p class="text-xs text-slate-500" x-text="trunk.notes"></p>
                                </div>
                                <div class="flex w-full flex-col gap-2 sm:flex-row sm:items-center">
                                    <input type="text" x-model="trunkTests[trunk.id].address"
                                        class="w-full rounded-lg border border-slate-200 px-3 py-2 text-xs text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                                        placeholder="Hostname or IP to evaluate">
                                    <button type="button" @click="runTrunkTest(trunk.id)"
                                        :disabled="trunkTests[trunk.id].loading"
                                        class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-4 py-2 text-xs font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700 disabled:cursor-not-allowed disabled:opacity-60">
                                        <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.8">
                                            <path stroke-linecap="round" stroke-linejoin="round" d="M10 4v12m6-6H4" />
                                        </svg>
                                        <span
                                            x-text="trunkTests[trunk.id].loading ? 'Evaluating…' : 'Run check'"></span>
                                    </button>
                                    <button type="button" @click="runTrunkOptions(trunk.id)"
                                        :disabled="trunkTests[trunk.id].options.loading"
                                        class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-4 py-2 text-xs font-semibold text-slate-600 transition hover:border-emerald-300 hover:text-emerald-700 disabled:cursor-not-allowed disabled:opacity-60">
                                        <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.8">
                                            <path stroke-linecap="round" stroke-linejoin="round" d="M5 10h10M10 5v10" />
                                        </svg>
                                        <span
                                            x-text="trunkTests[trunk.id].options.loading ? 'Probing…' : 'Send OPTIONS'"></span>
                                    </button>
                                </div>
                            </div>
                            <div class="mt-3 space-y-2 text-xs">
                                <template x-if="trunkTests[trunk.id].loading">
                                    <div class="rounded-lg border border-sky-200 bg-sky-50 px-4 py-3 text-sky-700">
                                        Evaluating address…
                                    </div>
                                </template>
                                <template x-if="trunkTests[trunk.id].error">
                                    <div class="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-rose-700"
                                        x-text="trunkTests[trunk.id].error"></div>
                                </template>
                                <template x-if="trunkTests[trunk.id].result">
                                    <div class="space-y-2 rounded-lg border border-slate-200 bg-white px-4 py-3">
                                        <div class="flex flex-wrap items-center justify-between gap-2">
                                            <span class="font-semibold text-slate-700"
                                                x-text="trunkTests[trunk.id].result.overall_match ? 'Address matches inbound definition' : 'No inbound definition matched'"></span>
                                            <span
                                                class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                                :class="trunkTests[trunk.id].result.overall_match ? 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-200' : 'bg-rose-50 text-rose-600 ring-1 ring-rose-200'">
                                                <span class="h-1.5 w-1.5 rounded-full"
                                                    :class="trunkTests[trunk.id].result.overall_match ? 'bg-emerald-500' : 'bg-rose-500'"></span>
                                                <span
                                                    x-text="trunkTests[trunk.id].result.overall_match ? 'Match' : 'No match'"></span>
                                            </span>
                                        </div>
                                        <div class="grid gap-2 text-[11px] text-slate-600">
                                            <div>
                                                <span class="font-semibold text-slate-700">Checked address:</span>
                                                <span x-text="trunkTests[trunk.id].result.address"></span>
                                            </div>
                                            <div>
                                                <span class="font-semibold text-slate-700">Resolved IPs:</span>
                                                <span
                                                    x-text="formatList(trunkTests[trunk.id].result.resolved_ips)"></span>
                                            </div>
                                            <div class="grid gap-2">
                                                <template x-for="item in trunkTests[trunk.id].result.ip_results"
                                                    :key="item.ip">
                                                    <div class="rounded border border-slate-200 bg-slate-50 p-2">
                                                        <div
                                                            class="flex items-center justify-between font-semibold text-slate-700">
                                                            <span x-text="item.ip"></span>
                                                            <span
                                                                x-text="item.matched ? 'Matched' : 'Not matched'"></span>
                                                        </div>
                                                        <div class="text-slate-500"
                                                            x-text="item.matched_sources.length ? 'Sources: ' + item.matched_sources.join(', ') : 'No matching sources'">
                                                        </div>
                                                    </div>
                                                </template>
                                            </div>
                                            <div class="flex flex-wrap gap-3">
                                                <div><span class="font-semibold text-slate-700">Inbound allowed:</span>
                                                    <span
                                                        x-text="trunkTests[trunk.id].result.allows_inbound ? 'Yes' : 'No'"></span>
                                                </div>
                                                <div><span class="font-semibold text-slate-700">Outbound allowed:</span>
                                                    <span
                                                        x-text="trunkTests[trunk.id].result.allows_outbound ? 'Yes' : 'No'"></span>
                                                </div>
                                                <template x-if="trunkTests[trunk.id].result.direction">
                                                    <div><span class="font-semibold text-slate-700">Direction:</span>
                                                        <span
                                                            x-text="formatDirection(trunkTests[trunk.id].result.direction)"></span>
                                                    </div>
                                                </template>
                                            </div>
                                            <div class="text-[11px] text-slate-400">
                                                Evaluated
                                                <span
                                                    x-text="formatDateTime(trunkTests[trunk.id].result.evaluated_at)"></span>
                                            </div>
                                        </div>
                                    </div>
                                </template>
                                <template x-if="trunkTests[trunk.id].options.loading">
                                    <div
                                        class="rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-emerald-700">
                                        Sending SIP OPTIONS probe…
                                    </div>
                                </template>
                                <template x-if="trunkTests[trunk.id].options.error">
                                    <div class="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-rose-700"
                                        x-text="trunkTests[trunk.id].options.error"></div>
                                </template>
                                <template x-if="trunkTests[trunk.id].options.result">
                                    <div class="space-y-2 rounded-lg border border-slate-200 bg-white px-4 py-3">
                                        <div class="flex flex-wrap items-center justify-between gap-2">
                                            <span class="font-semibold text-slate-700">SIP OPTIONS probe</span>
                                            <span
                                                class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                                :class="trunkTests[trunk.id].options.result.success ? 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-200' : 'bg-rose-50 text-rose-600 ring-1 ring-rose-200'">
                                                <span class="h-1.5 w-1.5 rounded-full"
                                                    :class="trunkTests[trunk.id].options.result.success ? 'bg-emerald-500' : 'bg-rose-500'"></span>
                                                <span
                                                    x-text="trunkTests[trunk.id].options.result.success ? 'Reachable' : 'No response'"></span>
                                            </span>
                                        </div>
                                        <div class="grid gap-2 text-[11px] text-slate-600">
                                            <div><span class="font-semibold text-slate-700">Target:</span>
                                                <span x-text="trunkTests[trunk.id].options.result.target_uri"></span>
                                            </div>
                                            <div><span class="font-semibold text-slate-700">Transport:</span>
                                                <span x-text="trunkTests[trunk.id].options.result.transport"></span>
                                            </div>
                                            <div class="grid gap-2">
                                                <template
                                                    x-for="attempt in trunkTests[trunk.id].options.result.attempts"
                                                    :key="attempt.destination">
                                                    <div class="rounded border border-slate-200 bg-slate-50 p-2">
                                                        <div
                                                            class="flex items-center justify-between font-semibold text-slate-700">
                                                            <span x-text="attempt.destination"></span>
                                                            <span
                                                                :class="attempt.success ? 'text-emerald-600' : 'text-rose-600'"
                                                                x-text="renderOptionsStatus(attempt)"></span>
                                                        </div>
                                                        <div class="grid gap-1 text-slate-500">
                                                            <div><span
                                                                    class="font-semibold text-slate-600">Latency:</span>
                                                                <span
                                                                    x-text="attempt.latency_ms ? attempt.latency_ms + ' ms' : '—'"></span>
                                                            </div>
                                                            <template x-if="attempt.error">
                                                                <div class="font-semibold text-rose-600"
                                                                    x-text="attempt.error"></div>
                                                            </template>
                                                            <template x-if="attempt.server">
                                                                <div><span
                                                                        class="font-semibold text-slate-600">Server:</span>
                                                                    <span x-text="attempt.server"></span>
                                                                </div>
                                                            </template>
                                                            <template x-if="attempt.raw_response">
                                                                <div class="text-slate-400"
                                                                    x-text="attempt.raw_response"></div>
                                                            </template>
                                                        </div>
                                                    </div>
                                                </template>
                                            </div>
                                        </div>
                                        <div class="text-[11px] text-slate-400">
                                            Probed
                                            <span
                                                x-text="formatDateTime(trunkTests[trunk.id].options.result.evaluated_at)"></span>
                                        </div>
                                    </div>
                                </template>
                            </div>
                        </article>
                    </template>
                </div>
            </div>
        </section>

        <section x-show="activeTab === 'routing'" x-cloak x-transition.opacity class="space-y-6">
            <div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
                <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
                    <div>
                        <h2 class="text-base font-semibold text-slate-900">Routing validation</h2>
                        <p class="text-xs text-slate-500">Predict how dialled numbers are normalised and which trunk or
                            route they match.</p>
                    </div>
                    <div class="flex flex-wrap items-center gap-2">
                        <button type="button" @click="loadSampleRouting" :disabled="!routingChecks.length"
                            class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700 disabled:cursor-not-allowed disabled:opacity-60">
                            Use sample
                        </button>
                        <button type="button" @click="routingAdvanced = !routingAdvanced"
                            class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-600 transition hover:border-amber-300 hover:text-amber-700">
                            <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                stroke-width="1.6">
                                <path stroke-linecap="round" stroke-linejoin="round" d="M10 4v12M4 10h12" />
                            </svg>
                            <span x-text="routingAdvanced ? 'Hide advanced' : 'Advanced options'"></span>
                        </button>
                    </div>
                </div>

                <form class="mt-4 grid gap-4 md:grid-cols-2" @submit.prevent="runRoutingCheck">
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Dialled number</span>
                        <input type="text" x-model.trim="routingInput"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                            placeholder="e.g. +14155550123">
                    </label>
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Caller SIP URI
                            (optional)</span>
                        <input type="text" x-model.trim="routingCaller"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                            placeholder="sip:caller@example.com">
                    </label>
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Direction</span>
                        <select x-model="routingDirection"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                            <option value="outbound">Outbound</option>
                            <option value="inbound">Inbound</option>
                            <option value="internal">Internal</option>
                        </select>
                    </label>
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Source IP
                            (optional)</span>
                        <input type="text" x-model.trim="routingSourceIp"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                            placeholder="203.0.113.10">
                    </label>
                    <template x-if="routingAdvanced">
                        <div class="md:col-span-2 grid gap-3 sm:grid-cols-2" x-transition>
                            <label class="flex flex-col gap-2">
                                <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Request URI
                                    (optional)</span>
                                <input type="text" x-model.trim="routingRequestUri"
                                    class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                                    placeholder="sip:destination@example.com">
                            </label>
                            <label class="flex flex-col gap-2">
                                <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Source
                                    trunk</span>
                                <select x-model="routingSourceTrunk"
                                    class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                                    <option value="">Auto detect</option>
                                    <template x-for="candidate in trunks" :key="candidate.id">
                                        <option :value="candidate.id" x-text="candidate.label || candidate.id"></option>
                                    </template>
                                </select>
                            </label>
                        </div>
                    </template>
                    <div class="md:col-span-2 flex flex-wrap items-center gap-3">
                        <button type="submit" :disabled="routingLoading"
                            class="inline-flex items-center gap-2 rounded-lg bg-sky-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60">
                            <template x-if="!routingLoading">
                                <span class="inline-flex items-center gap-2">
                                    <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                        stroke-width="1.8">
                                        <path stroke-linecap="round" stroke-linejoin="round" d="M10 4v12m6-6H4" />
                                    </svg>
                                    Evaluate
                                </span>
                            </template>
                            <template x-if="routingLoading">
                                <span class="inline-flex items-center gap-2">
                                    <svg class="h-4 w-4 animate-spin" viewBox="0 0 20 20" fill="none"
                                        stroke="currentColor" stroke-width="1.8">
                                        <path stroke-linecap="round" stroke-linejoin="round"
                                            d="M4 10a6 6 0 0 1 6-6m0-2a8 8 0 1 0 8 8" />
                                    </svg>
                                    Evaluating…
                                </span>
                            </template>
                        </button>
                    </div>
                    <div class="md:col-span-2 space-y-3">
                        <template x-if="routingLoading">
                            <div class="rounded-lg border border-sky-200 bg-sky-50 px-4 py-3 text-xs text-sky-700">
                                Evaluating routing…
                            </div>
                        </template>
                        <template x-if="routingError && !routingLoading">
                            <div class="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-xs text-rose-700"
                                x-text="routingError"></div>
                        </template>
                        <template x-if="routingResultSummary && !routingLoading">
                            <div
                                class="space-y-3 rounded-lg border border-slate-200 bg-white px-4 py-3 text-xs text-slate-600">
                                <div class="flex flex-wrap items-center gap-2">
                                    <span class="font-semibold text-slate-700">Rule:</span>
                                    <span x-text="routingResultSummary.rule || 'None'"></span>
                                    <span
                                        class="inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                        :class="routingResultSummary.badgeClass">
                                        <span class="h-1.5 w-1.5 rounded-full"
                                            :class="routingStatusDot(routingResultSummary.status)"></span>
                                        <span x-text="routingResultSummary.statusLabel"></span>
                                    </span>
                                </div>
                                <div class="grid gap-1 sm:grid-cols-2">
                                    <div><span class="font-semibold text-slate-700">Direction:</span>
                                        <span x-text="(routingResultSummary.direction || '—').toUpperCase()"></span>
                                    </div>
                                    <div><span class="font-semibold text-slate-700">Dialled:</span>
                                        <span x-text="routingResultSummary.input || '—'"></span>
                                    </div>
                                    <div><span class="font-semibold text-slate-700">Trunk:</span>
                                        <span x-text="routingResultSummary.trunk || '—'"></span>
                                    </div>
                                    <div><span class="font-semibold text-slate-700">Destination:</span>
                                        <span x-text="displayValue(routingResultSummary.destination)"></span>
                                    </div>
                                    <div><span class="font-semibold text-slate-700">Caller:</span>
                                        <span x-text="displayValue(routingResultSummary.caller)"></span>
                                    </div>
                                    <div><span class="font-semibold text-slate-700">Request URI:</span>
                                        <span x-text="displayValue(routingResultSummary.requestUri)"></span>
                                    </div>
                                    <div><span class="font-semibold text-slate-700">Source IP:</span>
                                        <span x-text="displayValue(routingResultSummary.sourceIp)"></span>
                                    </div>
                                    <template x-if="routingResultSummary.sourceTrunk">
                                        <div><span class="font-semibold text-slate-700">Provided source trunk:</span>
                                            <span x-text="routingResultSummary.sourceTrunk"></span>
                                        </div>
                                    </template>
                                    <template x-if="routingResultSummary.detectedTrunk">
                                        <div><span class="font-semibold text-slate-700">Detected source trunk:</span>
                                            <span x-text="routingResultSummary.detectedTrunk"></span>
                                        </div>
                                    </template>
                                </div>
                                <div class="text-[11px] text-slate-500">
                                    <span class="font-semibold text-slate-700">Outcome:</span>
                                    <span x-text="routingResultSummary.outcomeLabel"></span>
                                    <template x-if="routingResultSummary.defaultRoute">
                                        <span> · used default route</span>
                                    </template>
                                </div>
                                <div class="text-[11px] text-slate-500">
                                    <span class="font-semibold text-slate-700">Rewrites:</span>
                                    <span x-text="routingResultSummary.rewritesText"></span>
                                </div>
                                <template x-if="routingResultSummary.headers?.length">
                                    <div class="text-[11px] text-slate-500">
                                        <span class="font-semibold text-slate-700">Headers:</span>
                                        <span x-text="formatList(routingResultSummary.headers)"></span>
                                    </div>
                                </template>
                                <template x-if="routingResultSummary.credential">
                                    <div class="text-[11px] text-slate-500">
                                        <span class="font-semibold text-slate-700">Credential:</span>
                                        <span x-text="routingResultSummary.credential.username"></span>
                                        <template x-if="routingResultSummary.credential.realm">
                                            <span x-text="'@' + routingResultSummary.credential.realm"></span>
                                        </template>
                                    </div>
                                </template>
                                <template x-if="routingResultSummary.abort">
                                    <div class="text-[11px] text-rose-600">
                                        <span class="font-semibold">Rejected:</span>
                                        <span x-text="routingResultSummary.abort.code"></span>
                                        <template x-if="routingResultSummary.abort.reason">
                                            <span x-text="' · ' + routingResultSummary.abort.reason"></span>
                                        </template>
                                    </div>
                                </template>
                                <div class="text-[11px] text-slate-400">
                                    Evaluated <span x-text="formatDateTime(routingResultSummary.createdAt)"></span>
                                </div>
                                <template x-if="routingResultSummary.rewritesDiff?.length">
                                    <div class="rounded-lg border border-slate-200 bg-slate-50 p-3">
                                        <div class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
                                            Field changes
                                        </div>
                                        <div class="mt-2 grid gap-2">
                                            <template x-for="item in routingResultSummary.rewritesDiff"
                                                :key="item.field + (item.after || '') + (item.before || '')">
                                                <div class="rounded border border-slate-200 bg-white px-3 py-2">
                                                    <div class="text-[11px] font-semibold text-slate-700"
                                                        x-text="item.field"></div>
                                                    <div class="text-[11px] text-slate-500"><span
                                                            class="font-semibold">Before:</span>
                                                        <span x-text="displayValue(item.before)"></span>
                                                    </div>
                                                    <div class="text-[11px] text-slate-500"><span
                                                            class="font-semibold">After:</span>
                                                        <span x-text="displayValue(item.after)"></span>
                                                    </div>
                                                </div>
                                            </template>
                                        </div>
                                    </div>
                                </template>
                            </div>
                        </template>
                    </div>
                </form>
            </div>

            <div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
                <h3 class="text-base font-semibold text-slate-900">Recent checks</h3>
                <div class="mt-4 grid gap-3 md:grid-cols-2">
                    <template x-for="item in routingChecks" :key="item.id">
                        <div class="rounded-lg border border-slate-200 bg-white p-3">
                            <div
                                class="flex items-center justify-between text-[11px] uppercase tracking-wide text-slate-400">
                                <span x-text="item.direction"></span>
                                <span x-text="item.latency_ms + ' ms'"></span>
                            </div>
                            <div class="mt-1 text-sm font-semibold text-slate-800" x-text="item.input"></div>
                            <div class="text-xs text-slate-500">Route → <span class="font-semibold"
                                    x-text="item.matched_route"></span></div>
                            <div class="text-xs text-slate-500">Trunk → <span class="font-semibold"
                                    x-text="item.selected_trunk"></span></div>
                            <div class="text-xs text-slate-500">Caller → <span class="font-semibold"
                                    x-text="item.caller || '—'"></span></div>
                            <div class="text-xs text-slate-500">Source IP → <span class="font-semibold"
                                    x-text="item.sourceIp || '—'"></span></div>
                            <div class="mt-2 inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                :class="item.result === 'ok' ? 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-200' : 'bg-amber-50 text-amber-600 ring-1 ring-amber-200'">
                                <span x-text="item.result === 'ok' ? 'Pass' : 'Warning'"></span>
                            </div>
                        </div>
                    </template>
                    <template x-if="!routingChecks.length">
                        <div
                            class="rounded-lg border border-dashed border-slate-200 px-4 py-6 text-center text-xs text-slate-400">
                            No routing checks recorded yet.
                        </div>
                    </template>
                </div>
            </div>
        </section>

        <section x-show="activeTab === 'sip'" x-cloak x-transition.opacity class="space-y-6">
            <div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
                <div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
                    <div>
                        <h2 class="text-base font-semibold text-slate-900">Active SIP dialogs</h2>
                        <p class="text-xs text-slate-500">Inspect INVITE dialogs currently tracked by the SIP
                            server.</p>
                    </div>
                    <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-end">
                        <div class="flex items-center gap-2">
                            <input type="text" x-model="dialogCallIdInput" @keydown.enter.prevent="applyDialogFilter"
                                class="w-48 rounded-lg border border-slate-200 px-3 py-1.5 text-xs text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                                placeholder="Filter by Call-ID">
                            <button type="button" @click="applyDialogFilter"
                                :disabled="dialogsLoading || !(dialogCallIdInput && dialogCallIdInput.trim())"
                                class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-1.5 text-[11px] font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700 disabled:cursor-not-allowed disabled:opacity-60">
                                Search
                            </button>
                            <button type="button" @click="clearDialogFilter"
                                :disabled="dialogsLoading || (!dialogCallId && !(dialogCallIdInput && dialogCallIdInput.trim()))"
                                class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-1.5 text-[11px] font-semibold text-slate-500 transition hover:border-rose-300 hover:text-rose-600 disabled:cursor-not-allowed disabled:opacity-60">
                                Clear
                            </button>
                        </div>
                        <button type="button" @click="refreshDialogs" :disabled="dialogsLoading"
                            class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-1.5 text-xs font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700 disabled:cursor-not-allowed disabled:opacity-60">
                            <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                stroke-width="1.8">
                                <path stroke-linecap="round" stroke-linejoin="round"
                                    d="M4 4v4h4M16 16v-4h-4M5.172 14.828A6 6 0 0 0 15 10M4.998 10a6 6 0 0 1 9.828-4.828" />
                            </svg>
                            Refresh
                        </button>
                    </div>
                </div>

                <div class="mt-4 space-y-3">
                    <template x-if="dialogsError">
                        <div class="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-xs text-rose-700"
                            x-text="dialogsError"></div>
                    </template>

                    <template x-if="dialogsLoading">
                        <div class="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-500">
                            Loading dialogs…
                        </div>
                    </template>

                    <template x-if="!dialogsLoading && !dialogsError && dialogs.length">
                        <div class="space-y-3">
                            <div class="text-xs text-slate-500">
                                <span class="font-semibold text-slate-700" x-text="dialogs.length"></span>
                                active dialogs
                                <template x-if="dialogsMeta?.has_more">
                                    <span> · showing latest 20</span>
                                </template>
                                <template x-if="dialogCallId">
                                    <span> · filtered by <span class="font-mono text-slate-600"
                                            x-text="dialogCallId"></span></span>
                                </template>
                                · refreshed
                                <span x-text="formatDateTime(dialogsMeta?.generated_at)"></span>
                            </div>
                            <div class="grid gap-3">
                                <template x-for="item in dialogs" :key="item.id">
                                    <article class="rounded-lg border border-slate-200 bg-white p-4"
                                        x-data="{ expand: false }">
                                        <div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
                                            <div class="space-y-1">
                                                <div
                                                    class="flex flex-wrap items-center gap-2 text-[11px] font-semibold">
                                                    <span class="text-sm text-slate-800" x-text="item.call_id"></span>
                                                    <span class="rounded-full px-2 py-0.5"
                                                        :class="dialogRolePill(item.role)" x-text="item.role"></span>
                                                    <span class="rounded-full px-2 py-0.5"
                                                        :class="dialogStatePill(item.state)" x-text="item.state"></span>
                                                    <template x-if="item.state_detail">
                                                        <span class="text-slate-500" x-text="item.state_detail"></span>
                                                    </template>
                                                </div>
                                                <div class="text-xs text-slate-500">
                                                    <span class="font-semibold text-slate-700">From:</span>
                                                    <span x-text="item.from_display"></span>
                                                </div>
                                                <div class="text-xs text-slate-500">
                                                    <span class="font-semibold text-slate-700">To:</span>
                                                    <span x-text="item.to_display"></span>
                                                </div>
                                                <template x-if="item.remote_contact">
                                                    <div class="text-xs text-slate-500">
                                                        <span class="font-semibold text-slate-700">Remote
                                                            contact:</span>
                                                        <span x-text="item.remote_contact"></span>
                                                    </div>
                                                </template>
                                            </div>
                                            <div class="flex flex-col items-start gap-2 md:items-end">
                                                <button type="button" @click="expand = !expand"
                                                    class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-1.5 text-[11px] font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700">
                                                    <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none"
                                                        stroke="currentColor" stroke-width="1.6"
                                                        :class="expand ? 'rotate-180 transition' : 'transition'">
                                                        <path stroke-linecap="round" stroke-linejoin="round"
                                                            d="M6 8l4 4 4-4" />
                                                    </svg>
                                                    <span x-text="expand ? 'Hide SDP' : 'Show SDP'"></span>
                                                </button>
                                                <div class="text-[11px] text-slate-400">Dialog ID</div>
                                                <div class="max-w-xs break-all font-mono text-[11px] text-slate-500"
                                                    x-text="item.id"></div>
                                            </div>
                                        </div>
                                        <div class="mt-3 space-y-2" x-show="expand" x-transition>
                                            <template x-if="item.offer">
                                                <div>
                                                    <div
                                                        class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
                                                        Offer
                                                    </div>
                                                    <pre class="mt-1 max-h-48 overflow-auto rounded-lg bg-slate-900/90 p-3 text-[11px] leading-tight text-slate-100"
                                                        x-text="item.offer"></pre>
                                                </div>
                                            </template>
                                            <template x-if="item.answer">
                                                <div>
                                                    <div
                                                        class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
                                                        Answer
                                                    </div>
                                                    <pre class="mt-1 max-h-48 overflow-auto rounded-lg bg-slate-900/90 p-3 text-[11px] leading-tight text-slate-100"
                                                        x-text="item.answer"></pre>
                                                </div>
                                            </template>
                                        </div>
                                    </article>
                                </template>
                            </div>
                        </div>
                    </template>

                    <template x-if="!dialogsLoading && !dialogsError && !dialogs.length">
                        <div class="rounded-lg border border-dashed border-slate-200 px-4 py-6 text-center text-sm text-slate-400"
                            x-text="dialogCallId ? 'No dialogs match this Call-ID filter.' : 'No active dialogs reported.'">
                        </div>
                    </template>
                </div>
            </div>

            <div class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
                <div class="space-y-1">
                    <h2 class="text-base font-semibold text-slate-900">Locator registry</h2>
                    <p class="text-xs text-slate-500">Lookup registration bindings or clear stale records.</p>
                </div>
                <form class="mt-4 grid gap-4 md:grid-cols-2" @submit.prevent="performLocatorLookup()">
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">User</span>
                        <input type="text" x-model.trim="locatorUser"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                            placeholder="e.g. 1001 or 1001@example.com">
                    </label>
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">SIP URI</span>
                        <input type="text" x-model.trim="locatorUri"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                            placeholder="sip:1001@example.com">
                    </label>
                    <div class="md:col-span-2 flex flex-wrap items-center gap-3">
                        <button type="submit"
                            class="inline-flex items-center gap-2 rounded-lg bg-sky-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60"
                            :disabled="locatorLoading || locatorClearing || !hasLocatorInput">
                            <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                stroke-width="1.8">
                                <path stroke-linecap="round" stroke-linejoin="round"
                                    d="m13 13 4 4m-2-7A5 5 0 1 1 4 7a5 5 0 0 1 11 0Z" />
                            </svg>
                            Lookup registration
                        </button>
                        <button type="button" @click="clearLocator"
                            class="inline-flex items-center gap-2 rounded-lg border border-rose-200 px-4 py-2 text-sm font-semibold text-rose-600 transition hover:border-rose-300 hover:text-rose-700 disabled:cursor-not-allowed disabled:opacity-60"
                            :disabled="locatorClearing || !hasLocatorInput">
                            <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                stroke-width="1.8">
                                <path stroke-linecap="round" stroke-linejoin="round"
                                    d="M6 6h8m-7 0v8m6-8v8M4 6h12M5 6l1-2h8l1 2m-2 0v9a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V6" />
                            </svg>
                            Clear registration
                        </button>
                        <button type="button" @click="resetLocator"
                            class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-600 transition hover:border-slate-300 hover:text-slate-700"
                            :disabled="locatorLoading || locatorClearing">
                            <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                stroke-width="1.8">
                                <path stroke-linecap="round" stroke-linejoin="round"
                                    d="M4.5 9.5a5.5 5.5 0 1 1 1.742 4.007M4.5 9.5H2m0 0V6" />
                            </svg>
                            Reset
                        </button>
                    </div>
                </form>

                <div class="mt-4 space-y-3">
                    <template x-if="locatorError">
                        <div class="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-xs text-rose-700"
                            x-text="locatorError"></div>
                    </template>
                    <template x-if="locatorClearFeedback">
                        <div class="rounded-lg px-4 py-3 text-xs font-semibold"
                            :class="locatorClearFeedback?.variant === 'success' ? 'border border-emerald-200 bg-emerald-50 text-emerald-700' : 'border border-amber-200 bg-amber-50 text-amber-700'"
                            x-text="locatorClearFeedback?.message"></div>
                    </template>
                    <template x-if="locatorLoading">
                        <div class="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-500">
                            Loading locator records…
                        </div>
                    </template>
                    <template x-if="locatorResult">
                        <div class="space-y-3">
                            <div
                                class="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-xs text-slate-600">
                                <div class="flex flex-wrap items-center gap-3">
                                    <div>
                                        <span class="font-semibold text-slate-700">Bindings:</span>
                                        <span x-text="locatorResult.total"></span>
                                    </div>
                                    <div>
                                        <span class="font-semibold text-slate-700">Generated:</span>
                                        <span x-text="formatDateTime(locatorResult.generated_at)"></span>
                                    </div>
                                    <div>
                                        <span class="font-semibold text-slate-700">Query:</span>
                                        <span x-text="locatorResult.query.uri"></span>
                                    </div>
                                </div>
                            </div>
                            <template x-if="locatorResult.records.length">
                                <div class="space-y-3">
                                    <template x-for="record in locatorResult.records" :key="record.binding_key">
                                        <article class="rounded-lg border border-slate-200 bg-white p-4">
                                            <div
                                                class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
                                                <div class="space-y-1">
                                                    <div class="font-semibold text-slate-800"
                                                        x-text="record.binding_key"></div>
                                                    <div class="text-xs text-slate-500">
                                                        <span class="font-semibold text-slate-700">AoR:</span>
                                                        <span x-text="record.aor"></span>
                                                    </div>
                                                    <template x-if="record.destination">
                                                        <div class="text-xs text-slate-500">
                                                            <span
                                                                class="font-semibold text-slate-700">Destination:</span>
                                                            <span x-text="record.destination"></span>
                                                        </div>
                                                    </template>
                                                    <template x-if="record.contact">
                                                        <div class="text-xs text-slate-500">
                                                            <span class="font-semibold text-slate-700">Contact:</span>
                                                            <span x-text="record.contact"></span>
                                                        </div>
                                                    </template>
                                                    <template x-if="record.user_agent">
                                                        <div class="text-xs text-slate-500">
                                                            <span class="font-semibold text-slate-700">User
                                                                agent:</span>
                                                            <span x-text="record.user_agent"></span>
                                                        </div>
                                                    </template>
                                                </div>
                                                <div class="flex flex-col items-start gap-1 md:items-end">
                                                    <div class="rounded-full px-2 py-0.5 text-[11px] font-semibold"
                                                        :class="record.supports_webrtc ? 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-200' : 'bg-slate-100 text-slate-600 ring-1 ring-slate-200'">
                                                        <span
                                                            x-text="record.supports_webrtc ? 'WebRTC capable' : 'SIP only'"></span>
                                                    </div>
                                                    <div class="text-[11px] text-slate-400">
                                                        Expires in <span x-text="record.expires"></span> s
                                                    </div>
                                                    <template
                                                        x-if="record.age_seconds !== null && record.age_seconds !== undefined">
                                                        <div class="text-[11px] text-slate-400">
                                                            Age <span
                                                                x-text="formatDuration(record.age_seconds)"></span>
                                                        </div>
                                                    </template>
                                                </div>
                                            </div>
                                            <div class="mt-3 grid gap-2 text-xs text-slate-500 md:grid-cols-2">
                                                <template x-if="record.transport">
                                                    <div>
                                                        <span class="font-semibold text-slate-700">Transport:</span>
                                                        <span x-text="record.transport"></span>
                                                    </div>
                                                </template>
                                                <template x-if="record.instance_id">
                                                    <div>
                                                        <span class="font-semibold text-slate-700">Instance ID:</span>
                                                        <span x-text="record.instance_id"></span>
                                                    </div>
                                                </template>
                                                <template x-if="record.gruu">
                                                    <div>
                                                        <span class="font-semibold text-slate-700">GRUU:</span>
                                                        <span x-text="record.gruu"></span>
                                                    </div>
                                                </template>
                                                <template x-if="record.temp_gruu">
                                                    <div>
                                                        <span class="font-semibold text-slate-700">Temp GRUU:</span>
                                                        <span x-text="record.temp_gruu"></span>
                                                    </div>
                                                </template>
                                                <template x-if="record.registered_aor">
                                                    <div>
                                                        <span class="font-semibold text-slate-700">Registered
                                                            AoR:</span>
                                                        <span x-text="record.registered_aor"></span>
                                                    </div>
                                                </template>
                                                <template x-if="record.path.length">
                                                    <div class="md:col-span-2">
                                                        <span class="font-semibold text-slate-700">Path:</span>
                                                        <span x-text="record.path.join(' → ')"></span>
                                                    </div>
                                                </template>
                                                <template x-if="record.service_route.length">
                                                    <div class="md:col-span-2">
                                                        <span class="font-semibold text-slate-700">Service
                                                            route:</span>
                                                        <span x-text="record.service_route.join(' → ')"></span>
                                                    </div>
                                                </template>
                                                <template x-if="record.contact_params">
                                                    <div class="md:col-span-2">
                                                        <span class="font-semibold text-slate-700">Contact
                                                            params:</span>
                                                        <span
                                                            x-text="formatContactParams(record.contact_params)"></span>
                                                    </div>
                                                </template>
                                            </div>
                                        </article>
                                    </template>
                                </div>
                            </template>
                            <template x-if="!locatorResult.records.length">
                                <div
                                    class="rounded-lg border border-dashed border-slate-200 px-4 py-6 text-center text-sm text-slate-400">
                                    No registration bindings found.
                                </div>
                            </template>
                        </div>
                    </template>
                </div>
            </div>
        </section>

        <section x-show="activeTab === 'dialer'" x-cloak x-transition.opacity class="space-y-6">
            <div class="max-w-3xl rounded-xl bg-white p-6 shadow-sm ring-1 ring-black/5">
                <h2 class="text-base font-semibold text-slate-900">Dial a synthetic call</h2>
                <p class="mt-1 text-xs text-slate-500">Choose a trunk, set caller and callee details, and simulate a
                    call flow. Signalling remains local to the console preview.</p>
                <form class="mt-4 space-y-4" @submit.prevent="startDialTest">
                    <div class="grid gap-4 md:grid-cols-2">
                        <label class="flex flex-col gap-2">
                            <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Caller SIP
                                URI</span>
                            <input type="text" x-model.trim="dial.caller"
                                class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                                placeholder="sip:1001@example.com">
                        </label>
                        <label class="flex flex-col gap-2">
                            <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Callee SIP
                                URI</span>
                            <div class="flex gap-2">
                                <input type="text" x-model.trim="dial.destination"
                                    class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                                    placeholder="sip:1000@example.com">
                                <div class="relative" x-data="{ open: false }" @click.outside="open = false">
                                    <button type="button" @click="open = !open"
                                        class="inline-flex h-10 w-10 items-center justify-center rounded-lg border border-slate-200 text-slate-500 transition hover:border-sky-300 hover:text-sky-700">
                                        <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.8">
                                            <path stroke-linecap="round" stroke-linejoin="round" d="M6 8l4 4 4-4" />
                                        </svg>
                                    </button>
                                    <div x-cloak x-show="open" x-transition.origin.top.right
                                        class="absolute right-0 z-20 mt-2 w-56 rounded-lg border border-slate-200 bg-white p-2 text-sm shadow-xl">
                                        <template x-for="sample in dialer.destination_samples" :key="sample.value">
                                            <button type="button" @click="dial.destination = sample.value; open = false"
                                                class="flex w-full flex-col gap-0.5 rounded-md px-2 py-1.5 text-left transition hover:bg-slate-100">
                                                <span class="text-xs font-semibold text-slate-600"
                                                    x-text="sample.label"></span>
                                                <span class="font-mono text-xs text-slate-500"
                                                    x-text="sample.value"></span>
                                            </button>
                                        </template>
                                    </div>
                                </div>
                            </div>
                        </label>
                    </div>
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Source</span>
                        <select x-model="dial.source"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                            <template x-for="item in dialer.source_options" :key="item">
                                <option :value="item" x-text="item"></option>
                            </template>
                        </select>
                    </label>
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Preferred
                            trunk</span>
                        <select x-model="dial.trunk"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                            <template x-for="option in dialer.trunk_options" :key="option.id">
                                <option :value="option.id" x-text="option.label"></option>
                            </template>
                        </select>
                    </label>
                    <label class="flex flex-col gap-2">
                        <span class="text-xs font-semibold uppercase tracking-wide text-slate-400">Preferred
                            codec</span>
                        <select x-model="dial.codec"
                            class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                            <template x-for="item in dialer.codec_options" :key="item">
                                <option :value="item" x-text="item"></option>
                            </template>
                        </select>
                    </label>
                    <template x-if="dialError">
                        <div class="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-xs text-rose-700"
                            x-text="dialError"></div>
                    </template>
                    <button type="submit" :disabled="dialing"
                        class="inline-flex w-full items-center justify-center gap-2 rounded-lg bg-emerald-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-emerald-500 focus:outline-none focus:ring-2 focus:ring-emerald-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60">
                        <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M5 4h2l2 12h4l2-12h2" />
                            <path stroke-linecap="round" stroke-linejoin="round" d="M7 4L9 2h2l2 2" />
                        </svg>
                        Start test call
                    </button>
                </form>
                <div class="mt-4" x-show="dialResult">
                    <div class="rounded-lg border px-4 py-3 text-xs"
                        :class="(dialResult && dialResult.status === 'pass') ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-rose-200 bg-rose-50 text-rose-700'">
                        <div class="flex items-center justify-between">
                            <span class="font-semibold"
                                x-text="dialResult && dialResult.status === 'pass' ? 'Simulation successful' : 'Simulation failed'"></span>
                            <span x-text="dialResult ? dialResult.timestamp : ''"></span>
                        </div>
                        <p class="mt-1" x-text="dialResult ? dialResult.details : ''"></p>
                        <template x-if="dialResult && dialResult.summary">
                            <div class="mt-3 grid gap-1 text-[11px] text-slate-500 sm:grid-cols-2">
                                <div><span class="font-semibold text-slate-700">Caller:</span>
                                    <span x-text="dialResult.summary.caller"></span>
                                </div>
                                <div><span class="font-semibold text-slate-700">Callee:</span>
                                    <span x-text="dialResult.summary.callee"></span>
                                </div>
                                <div><span class="font-semibold text-slate-700">Trunk:</span>
                                    <span x-text="dialResult.summary.trunk"></span>
                                </div>
                                <div><span class="font-semibold text-slate-700">Codec:</span>
                                    <span x-text="dialResult.summary.codec"></span>
                                </div>
                            </div>
                        </template>
                    </div>
                </div>
            </div>
        </section>

    </div>

    <script>
        document.addEventListener('alpine:init', () => {
            Alpine.data('testingConsole', (payload) => ({
                init() {
                    const data = typeof payload === 'string' ? JSON.parse(payload || '{}') : (payload || {});
                    this.lastAudit = data.last_audit || null;
                    this.trunks = Array.isArray(data.trunks) ? data.trunks : [];
                    this.routingChecks = Array.isArray(data.routing_checks) ? data.routing_checks : [];
                    this.initializeTrunkTests();
                    this.dialer = data.dialer || { source_options: [], destination_samples: [], trunk_options: [] };
                    if (!Array.isArray(this.dialer.codec_options) || !this.dialer.codec_options.length) {
                        this.dialer.codec_options = [
                            'Opus',
                            'PCMU (G.711 μ-law)',
                            'PCMA (G.711 A-law)',
                            'G.722',
                            'G.729',
                        ];
                    }
                    this.dial.source = this.dialer.default_source || (this.dialer.source_options[0] || '');
                    this.dial.trunk = this.dialer.trunk_options?.[0]?.id || '';
                    this.dial.destination = this.dialer.destination_samples?.[0]?.value || '';
                    this.dial.caller = this.dialer.default_caller || '';
                    this.dial.codec = this.dialer.codec_options?.[0] || '';
                    const firstCheck = this.routingChecks?.[0] || {};
                    this.routingInput = firstCheck.input || '';
                    this.routingDirection = firstCheck.direction || 'outbound';
                    this.routingCaller = firstCheck.caller && firstCheck.caller !== '—' ? firstCheck.caller : '';
                    this.routingRequestUri = '';
                    this.routingSourceTrunk = '';
                    this.routingSourceIp = firstCheck.sourceIp && firstCheck.sourceIp !== '—' ? firstCheck.sourceIp : '';
                    this.routingResultSummary = null;
                    this.routingError = null;
                    this.basePath = "{{ base_path | default('/console')|safe }}"
                    this.$watch('activeTab', (value) => {
                        if (value === 'sip') {
                            this.ensureDialogsLoaded();
                        }
                    });
                    this.$watch('trunks', () => {
                        this.initializeTrunkTests();
                    });
                },
                basePath: '',
                lastAudit: null,
                trunks: [],
                routingChecks: [],
                trunkTests: {},
                routingInput: '',
                routingDirection: 'outbound',
                routingAdvanced: false,
                routingCaller: '',
                routingRequestUri: '',
                routingSourceTrunk: '',
                routingSourceIp: '',
                routingLoading: false,
                routingError: null,
                routingResultSummary: null,
                dialogCallIdInput: '',
                dialogCallId: '',
                dialer: {},
                dial: { source: '', destination: '', trunk: '', caller: '', codec: '' },
                dialing: false,
                dialResult: null,
                dialError: null,
                tabs: [
                    { id: 'trunks', label: 'Trunks' },
                    { id: 'routing', label: 'Routing' },
                    { id: 'sip', label: 'SIP State' },
                    { id: 'dialer', label: 'Dialer' },
                ],
                activeTab: 'trunks',
                selectTab(id) {
                    this.activeTab = id;
                    if (id === 'sip') {
                        this.ensureDialogsLoaded();
                    }
                },
                get healthyTrunks() {
                    return this.trunks.filter((item) => this.statusLabel(item.status) === 'Healthy').length;
                },
                get warningTrunks() {
                    return this.trunks.filter((item) => this.statusLabel(item.status) !== 'Healthy').length;
                },
                get hasDialogFilter() {
                    return Boolean(this.dialogCallId);
                },
                get hasLocatorInput() {
                    return Boolean(
                        (this.locatorUser && this.locatorUser.trim()) || (this.locatorUri && this.locatorUri.trim())
                    );
                },
                dialogs: [],
                dialogsMeta: null,
                dialogsLoading: false,
                dialogsError: null,
                dialogsFetchedAt: null,
                locatorUser: '',
                locatorUri: '',
                locatorLoading: false,
                locatorClearing: false,
                locatorError: null,
                locatorResult: null,
                locatorClearFeedback: null,
                statusLabel(value) {
                    switch ((value || '').toLowerCase()) {
                        case 'healthy':
                            return 'Healthy';
                        case 'warning':
                            return 'Warning';
                        case 'lab':
                            return 'Lab';
                        default:
                            return value || 'Unknown';
                    }
                },
                statusPill(value) {
                    const key = (value || '').toLowerCase();
                    if (key === 'healthy') {
                        return 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-200';
                    }
                    if (key === 'warning') {
                        return 'bg-amber-50 text-amber-600 ring-1 ring-amber-200';
                    }
                    if (key === 'lab') {
                        return 'bg-sky-50 text-sky-600 ring-1 ring-sky-200';
                    }
                    return 'bg-slate-100 text-slate-600 ring-1 ring-slate-200';
                },
                statusDot(value) {
                    switch ((value || '').toLowerCase()) {
                        case 'healthy':
                            return 'bg-emerald-500';
                        case 'warning':
                            return 'bg-amber-500';
                        case 'lab':
                            return 'bg-sky-500';
                        default:
                            return 'bg-slate-400';
                    }
                },
                initializeTrunkTests() {
                    const state = {};
                    if (Array.isArray(this.trunks)) {
                        this.trunks.forEach((trunk) => {
                            state[trunk.id] = {
                                address: trunk.ingress_ips?.[0] || trunk.egress || '',
                                loading: false,
                                error: null,
                                result: null,
                                options: {
                                    loading: false,
                                    error: null,
                                    result: null,
                                },
                            };
                        });
                    }
                    this.trunkTests = state;
                },
                ensureTrunkTestState(trunkId) {
                    if (this.trunkTests[trunkId]) {
                        return;
                    }
                    this.trunkTests = {
                        ...this.trunkTests,
                        [trunkId]: {
                            address: '',
                            loading: false,
                            error: null,
                            result: null,
                            options: {
                                loading: false,
                                error: null,
                                result: null,
                            },
                        },
                    };
                },
                async runTrunkTest(trunkId) {
                    this.ensureTrunkTestState(trunkId);
                    const state = this.trunkTests[trunkId];
                    if (!state) {
                        return;
                    }
                    const address = (state.address || '').trim();
                    if (!address) {
                        state.error = 'Provide a hostname or IP to evaluate.';
                        state.result = null;
                        this.trunkTests = { ...this.trunkTests };
                        return;
                    }
                    state.loading = true;
                    state.error = null;
                    state.result = null;
                    this.trunkTests = { ...this.trunkTests };
                    try {
                        const response = await fetch(this.buildUrl('/diagnostics/trunks/test'), {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            credentials: 'same-origin',
                            body: JSON.stringify({ trunk: trunkId, address }),
                        });
                        const data = await response.json().catch(() => null);
                        if (!response.ok) {
                            const message = data?.message || `Evaluation failed (${response.status})`;
                            throw new Error(message);
                        }
                        state.result = data;
                        state.error = null;
                    } catch (err) {
                        state.error = err?.message || 'Evaluation failed.';
                        state.result = null;
                    } finally {
                        state.loading = false;
                        this.trunkTests = { ...this.trunkTests };
                    }
                },
                async runTrunkOptions(trunkId) {
                    this.ensureTrunkTestState(trunkId);
                    const state = this.trunkTests[trunkId];
                    if (!state) {
                        return;
                    }
                    const payload = { trunk: trunkId };
                    const address = (state.address || '').trim();
                    if (address) {
                        payload.address = address;
                    }
                    state.options.loading = true;
                    state.options.error = null;
                    state.options.result = null;
                    this.trunkTests = { ...this.trunkTests };
                    try {
                        const response = await fetch(this.buildUrl('/diagnostics/trunks/options'), {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            credentials: 'same-origin',
                            body: JSON.stringify(payload),
                        });
                        const data = await response.json().catch(() => null);
                        if (!response.ok) {
                            const message = data?.message || `OPTIONS probe failed (${response.status})`;
                            throw new Error(message);
                        }
                        state.options.result = data;
                        state.options.error = null;
                    } catch (err) {
                        state.options.error = err?.message || 'OPTIONS probe failed.';
                        state.options.result = null;
                    } finally {
                        state.options.loading = false;
                        this.trunkTests = { ...this.trunkTests };
                    }
                },
                loadSampleRouting() {
                    if (!this.routingChecks.length) {
                        return;
                    }
                    const sample = this.routingChecks[0];
                    this.routingInput = sample.input;
                    this.routingDirection = sample.direction;
                    this.routingCaller = sample.caller && sample.caller !== '—' ? sample.caller : '';
                    this.routingRequestUri = '';
                    this.routingSourceTrunk = '';
                    this.routingSourceIp = sample.sourceIp && sample.sourceIp !== '—' ? sample.sourceIp : '';
                    this.routingAdvanced = false;
                    this.routingResultSummary = null;
                    this.routingError = null;
                },
                async runRoutingCheck() {
                    const callee = (this.routingInput || '').trim();
                    if (!callee) {
                        this.routingError = 'Provide a dialled number to continue.';
                        return;
                    }
                    const payload = {
                        callee,
                        direction: this.routingDirection,
                    };
                    const caller = (this.routingCaller || '').trim();
                    if (caller) {
                        payload.caller = caller;
                    }
                    const sourceIp = (this.routingSourceIp || '').trim();
                    if (sourceIp) {
                        payload.source_ip = sourceIp;
                    }
                    if (this.routingRequestUri && this.routingRequestUri.trim()) {
                        payload.request_uri = this.routingRequestUri.trim();
                    }
                    if (this.routingSourceTrunk) {
                        payload.source_trunk = this.routingSourceTrunk;
                    }
                    this.routingLoading = true;
                    this.routingError = null;
                    this.routingResultSummary = null;
                    const started = typeof performance !== 'undefined' ? performance.now() : Date.now();
                    try {
                        const response = await fetch(this.buildUrl('/diagnostics/routes/evaluate'), {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            credentials: 'same-origin',
                            body: JSON.stringify(payload),
                        });
                        const data = await response.json().catch(() => null);
                        if (!response.ok) {
                            const message = data?.message || `Routing evaluation failed (${response.status})`;
                            throw new Error(message);
                        }
                        const summary = this.transformRoutingResult(data, callee);
                        this.routingResultSummary = summary;
                        const ended = typeof performance !== 'undefined' ? performance.now() : Date.now();
                        const latencyMs = Math.max(1, Math.round(ended - started));
                        this.updateRoutingChecks(summary, latencyMs);
                    } catch (err) {
                        this.routingError = err?.message || 'Routing evaluation failed.';
                    } finally {
                        this.routingLoading = false;
                    }
                },
                startDialTest() {
                    const trunk = (this.dial.trunk || '').trim();
                    const caller = (this.dial.caller || '').trim();
                    const callee = (this.dial.destination || '').trim();
                    const codec = (this.dial.codec || '').trim();

                    if (!trunk) {
                        this.dialError = 'Select a trunk before starting the simulation.';
                        return;
                    }
                    if (!caller) {
                        this.dialError = 'Provide a caller SIP URI to continue.';
                        return;
                    }
                    if (!callee) {
                        this.dialError = 'Provide a callee SIP URI or number to continue.';
                        return;
                    }

                    this.dialError = null;
                    this.dialing = true;
                    this.dialResult = null;
                    window.setTimeout(() => {
                        this.dialing = false;
                        const success = Math.random() > 0.15;
                        const codecLabel = codec || 'Auto';
                        const detail = success
                            ? `Simulated ${callee} via ${trunk} using ${codecLabel} · MOS 4.${Math.floor(Math.random() * 2) + 2}`
                            : 'Synthetic call reported 486 Busy Here (simulated).';
                        this.dialResult = {
                            status: success ? 'pass' : 'fail',
                            details: detail,
                            timestamp: new Date().toLocaleTimeString(),
                            summary: {
                                caller,
                                callee,
                                trunk,
                                codec: codecLabel,
                            },
                        };
                    }, 1100);
                },
                updateRoutingChecks(summary, latencyMs) {
                    if (!summary) {
                        return;
                    }
                    const rewrites = Array.isArray(summary.rewriteOperations) && summary.rewriteOperations.length
                        ? summary.rewriteOperations
                        : ['no rewrite'];
                    const card = {
                        id: `dynamic-${Date.now()}`,
                        input: summary.input,
                        direction: summary.direction,
                        matched_route: summary.rule || 'None',
                        selected_trunk: summary.trunk || '—',
                        caller: summary.caller || null,
                        sourceIp: summary.sourceIp || null,
                        rewrites,
                        result: summary.status === 'success' ? 'ok' : 'warning',
                        latency_ms: latencyMs,
                    };
                    this.routingChecks = [card, ...this.routingChecks].slice(0, 8);
                },
                transformRoutingResult(data, input) {
                    const outcome = data?.outcome || {};
                    const type = (outcome.type || 'not_handled').toLowerCase();
                    let status = 'warning';
                    let statusLabel = 'Not handled';
                    let badgeClass = 'bg-amber-50 text-amber-600 ring-1 ring-amber-200';
                    let outcomeLabel = 'No matching route';
                    let historyDetails = 'No routing rule matched.';
                    const defaultRoute = Boolean(data?.used_default_route);
                    if (type === 'forward') {
                        status = defaultRoute ? 'warning' : 'success';
                        statusLabel = defaultRoute ? 'Forwarded (default)' : 'Forwarded';
                        badgeClass = defaultRoute
                            ? 'bg-amber-50 text-amber-600 ring-1 ring-amber-200'
                            : 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-200';
                        const destination = outcome.destination || data?.selected_trunk || '—';
                        outcomeLabel = defaultRoute
                            ? `Forwarded via default route (${destination})`
                            : `Forwarded to ${destination}`;
                        historyDetails = `Forward via ${data?.selected_trunk || '—'} → ${destination}`;
                    } else if (type === 'abort') {
                        status = 'error';
                        statusLabel = 'Rejected';
                        badgeClass = 'bg-rose-50 text-rose-600 ring-1 ring-rose-200';
                        const code = outcome.code || '';
                        const reason = outcome.reason ? ` ${outcome.reason}` : '';
                        outcomeLabel = `Rejected ${code}${reason}`.trim();
                        historyDetails = outcomeLabel;
                    }
                    const rewriteOperations = Array.isArray(data?.rewrite_operations)
                        ? data.rewrite_operations
                        : [];
                    const rewritesDiff = Array.isArray(data?.rewrites) ? data.rewrites : [];
                    const summary = {
                        rule: data?.matched_rule || (defaultRoute ? 'Default route' : null),
                        status,
                        statusLabel,
                        badgeClass,
                        direction: data?.direction || '',
                        input,
                        trunk: data?.selected_trunk || null,
                        destination: outcome.destination || null,
                        caller: data?.caller || null,
                        requestUri: data?.request_uri || null,
                        sourceIp: data?.source_ip || null,
                        sourceTrunk: data?.source_trunk || null,
                        detectedTrunk: data?.detected_trunk || null,
                        defaultRoute,
                        outcomeLabel,
                        historyDetails,
                        createdAt: data?.evaluated_at || null,
                        rewriteOperations,
                        rewritesDiff,
                        rewritesText: rewriteOperations.length
                            ? rewriteOperations.join(', ')
                            : 'No rewrite changes',
                        headers: Array.isArray(outcome.headers) ? outcome.headers : [],
                        credential: outcome.credential || null,
                        abort: type === 'abort'
                            ? { code: outcome.code, reason: outcome.reason || null }
                            : null,
                    };
                    return summary;
                },
                routingStatusDot(status) {
                    switch ((status || '').toLowerCase()) {
                        case 'success':
                            return 'bg-emerald-500';
                        case 'error':
                            return 'bg-rose-500';
                        case 'warning':
                            return 'bg-amber-500';
                        default:
                            return 'bg-slate-400';
                    }
                },
                formatList(value) {
                    if (!value || (Array.isArray(value) && !value.length)) {
                        return '—';
                    }
                    if (Array.isArray(value)) {
                        return value.filter((item) => item !== null && item !== undefined && item !== '')
                            .map((item) => String(item))
                            .join(', ');
                    }
                    return String(value);
                },
                renderOptionsStatus(attempt) {
                    if (!attempt) {
                        return '—';
                    }
                    const code = attempt.status_code ? String(attempt.status_code) : '';
                    const reason = attempt.reason ? String(attempt.reason) : '';
                    const base = code ? `${code}${reason ? ` ${reason}` : ''}` : '';
                    if (attempt.success) {
                        return base || 'Success';
                    }
                    return attempt.error || base || 'No response';
                },
                displayValue(value) {
                    if (!value && value !== 0) {
                        return '—';
                    }
                    if (Array.isArray(value)) {
                        return this.formatList(value);
                    }
                    if (typeof value === 'object') {
                        return JSON.stringify(value);
                    }
                    return String(value);
                },
                formatDirection(value) {
                    if (!value) {
                        return '—';
                    }
                    if (Array.isArray(value)) {
                        return value.map((item) => this.formatDirection(item)).join(' / ');
                    }
                    const key = String(value).toLowerCase();
                    switch (key) {
                        case 'inbound':
                            return 'Inbound';
                        case 'outbound':
                            return 'Outbound';
                        case 'internal':
                            return 'Internal';
                        case 'bidirectional':
                            return 'Bidirectional';
                        default:
                            return String(value);
                    }
                },
                applyDialogFilter() {
                    const trimmed = (this.dialogCallIdInput || '').trim();
                    this.dialogCallIdInput = trimmed;
                    if (!trimmed) {
                        if (this.dialogCallId) {
                            this.dialogCallId = '';
                            this.refreshDialogs();
                        }
                        return;
                    }
                    if (trimmed !== this.dialogCallId) {
                        this.dialogCallId = trimmed;
                    }
                    this.refreshDialogs();
                },
                clearDialogFilter() {
                    if (!this.dialogCallId && !(this.dialogCallIdInput && this.dialogCallIdInput.trim())) {
                        return;
                    }
                    this.dialogCallIdInput = '';
                    if (this.dialogCallId) {
                        this.dialogCallId = '';
                    }
                    this.refreshDialogs();
                },
                ensureDialogsLoaded() {
                    if (this.dialogsFetchedAt || this.dialogsLoading) {
                        return;
                    }
                    this.fetchDialogs();
                },
                buildUrl(path) {
                    const clean = path.startsWith('/') ? path : `/${path}`;
                    return `${this.basePath}${clean}`;
                },
                async fetchDialogs(force = false) {
                    if (this.dialogsLoading) {
                        return;
                    }
                    if (!force && this.dialogsFetchedAt) {
                        return;
                    }
                    this.dialogsLoading = true;
                    this.dialogsError = null;
                    try {
                        const params = new URLSearchParams();
                        if (this.dialogCallId) {
                            params.set('call_id', this.dialogCallId);
                        }
                        params.set('limit', '20');
                        let url = this.buildUrl('/diagnostics/dialogs');
                        const query = params.toString();
                        if (query) {
                            url = `${url}?${query}`;
                        }
                        const response = await fetch(url, {
                            credentials: 'same-origin',
                        });
                        const data = await response.json().catch(() => null);
                        if (!response.ok) {
                            const message = data?.message || `Failed to load dialogs (${response.status})`;
                            throw new Error(message);
                        }
                        this.dialogs = Array.isArray(data?.items) ? data.items : [];
                        this.dialogsMeta = {
                            total: data?.total || 0,
                            generated_at: data?.generated_at || null,
                            has_more: Boolean(data?.has_more),
                        };
                        this.dialogsFetchedAt = data?.generated_at || new Date().toISOString();
                    } catch (err) {
                        this.dialogsError = err?.message || 'Failed to load dialogs.';
                        this.dialogs = [];
                        this.dialogsMeta = null;
                        this.dialogsFetchedAt = null;
                    } finally {
                        this.dialogsLoading = false;
                    }
                },
                refreshDialogs() {
                    this.dialogsFetchedAt = null;
                    this.fetchDialogs(true);
                },
                dialogRolePill(role) {
                    const key = (role || '').toLowerCase();
                    if (key === 'server') {
                        return 'bg-slate-100 text-slate-600 ring-1 ring-slate-200';
                    }
                    if (key === 'client') {
                        return 'bg-sky-50 text-sky-600 ring-1 ring-sky-200';
                    }
                    return 'bg-slate-100 text-slate-600 ring-1 ring-slate-200';
                },
                dialogStatePill(state) {
                    const key = (state || '').toLowerCase();
                    if (key === 'confirmed') {
                        return 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-200';
                    }
                    if (['early', 'trying', 'calling'].includes(key)) {
                        return 'bg-sky-50 text-sky-600 ring-1 ring-sky-200';
                    }
                    if (['waitack', 'updated', 'notify', 'info', 'options'].includes(key)) {
                        return 'bg-amber-50 text-amber-600 ring-1 ring-amber-200';
                    }
                    return 'bg-slate-100 text-slate-600 ring-1 ring-slate-200';
                },
                async performLocatorLookup(options = {}) {
                    const preserveClearFeedback = Boolean(options?.preserveClearFeedback);
                    if (!this.hasLocatorInput) {
                        this.locatorError = 'Provide a user or SIP URI to continue.';
                        return false;
                    }
                    this.locatorError = null;
                    if (!preserveClearFeedback) {
                        this.locatorClearFeedback = null;
                    }
                    this.locatorLoading = true;
                    try {
                        const response = await fetch(this.buildUrl('/diagnostics/locator/lookup'), {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            credentials: 'same-origin',
                            body: JSON.stringify({
                                user: this.locatorUser || null,
                                uri: this.locatorUri || null,
                            }),
                        });
                        const data = await response.json().catch(() => null);
                        if (!response.ok) {
                            const message = data?.message || `Lookup failed (${response.status})`;
                            throw new Error(message);
                        }
                        this.locatorResult = data;
                        return true;
                    } catch (err) {
                        this.locatorError = err?.message || 'Lookup failed.';
                        this.locatorResult = null;
                        return false;
                    } finally {
                        this.locatorLoading = false;
                    }
                },
                async clearLocator() {
                    if (!this.hasLocatorInput) {
                        this.locatorError = 'Provide a user or SIP URI before clearing.';
                        return;
                    }
                    this.locatorError = null;
                    this.locatorClearing = true;
                    try {
                        const response = await fetch(this.buildUrl('/diagnostics/locator/clear'), {
                            method: 'POST',
                            headers: { 'Content-Type': 'application/json' },
                            credentials: 'same-origin',
                            body: JSON.stringify({
                                user: this.locatorUser || null,
                                uri: this.locatorUri || null,
                            }),
                        });
                        const data = await response.json().catch(() => null);
                        if (!response.ok) {
                            const message = data?.message || `Clear failed (${response.status})`;
                            throw new Error(message);
                        }
                        const removed = Boolean(data?.removed);
                        const remaining = typeof data?.remaining === 'number' ? data.remaining : null;
                        this.locatorClearFeedback = {
                            message: removed
                                ? remaining
                                    ? `Cleared registration; ${remaining} binding(s) remain.`
                                    : 'Cleared registration.'
                                : 'No registration matched the request.',
                            variant: removed ? 'success' : 'warning',
                        };
                        await this.performLocatorLookup({ preserveClearFeedback: true });
                    } catch (err) {
                        this.locatorError = err?.message || 'Clear failed.';
                    } finally {
                        this.locatorClearing = false;
                    }
                },
                resetLocator() {
                    this.locatorUser = '';
                    this.locatorUri = '';
                    this.locatorResult = null;
                    this.locatorError = null;
                    this.locatorClearFeedback = null;
                },
                formatDateTime(value) {
                    if (!value) {
                        return '—';
                    }
                    try {
                        const date = new Date(value);
                        if (Number.isNaN(date.getTime())) {
                            return value;
                        }
                        return date.toLocaleString();
                    } catch (err) {
                        return value;
                    }
                },
                formatDuration(value) {
                    if (value === null || value === undefined) {
                        return '—';
                    }
                    const seconds = Number(value);
                    if (!Number.isFinite(seconds)) {
                        return '—';
                    }
                    if (seconds >= 3600) {
                        const hours = Math.floor(seconds / 3600);
                        const minutes = Math.floor((seconds % 3600) / 60);
                        return `${hours}h ${minutes}m`;
                    }
                    if (seconds >= 60) {
                        const minutes = Math.floor(seconds / 60);
                        const secs = Math.floor(seconds % 60);
                        return `${minutes}m ${secs}s`;
                    }
                    return `${Math.floor(seconds)}s`;
                },
                formatContactParams(params) {
                    if (!params || typeof params !== 'object') {
                        return '—';
                    }
                    const entries = Object.entries(params).map(([key, value]) => `${key}=${value}`);
                    return entries.length ? entries.join(', ') : '—';
                },
            }));
        });
    </script>
    {% endblock %}